#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Wumpus World (4x4) — Textspiel
Regeln (kurz):
- Wahrnehmung als 5-Tupel [Stench, Breeze, Glitter, Bump, Scream] mit Strings oder None.
- Start: Agent in [1,1], Blickrichtung Osten. Es gibt genau 1 Wumpus (lebt zu Beginn), 1 Gold, 3 Fallgruben.
- Ziel: Gold holen und bei [1,1] "climb" (verlasse die Höhle).
- Aktionen: forward (f), turn_left (l), turn_right (r), grab (g), shoot (s; 1 Pfeil), climb (c), quit (q)
- Der Agent stirbt, wenn er in eine Fallgrube fällt oder dem lebenden Wumpus begegnet.
- Bump: wenn man in eine Wand läuft (nur für 1 Zug sichtbar).
- Scream: wenn Wumpus durch Pfeil stirbt (nur für 1 Zug überall hörbar).
- Der Agent kann seinen Standort NICHT wahrnehmen.
"""
from __future__ import annotations
import random
from dataclasses import dataclass
from typing import List, Optional, Set, Tuple

Coord = Tuple[int, int]

DIRECTIONS = ['N', 'E', 'S', 'W']  # Reihenfolge für Links/Rechts-Drehung

def left_of(dir_: str) -> str:
    return DIRECTIONS[(DIRECTIONS.index(dir_) - 1) % 4]

def right_of(dir_: str) -> str:
    return DIRECTIONS[(DIRECTIONS.index(dir_) + 1) % 4]

def forward_from(pos: Coord, dir_: str) -> Coord:
    x, y = pos
    if dir_ == 'N':
        return (x, y + 1)
    if dir_ == 'E':
        return (x + 1, y)
    if dir_ == 'S':
        return (x, y - 1)
    if dir_ == 'W':
        return (x - 1, y)
    raise ValueError("Unknown direction")

def in_bounds(p: Coord) -> bool:
    x, y = p
    return 1 <= x <= 4 and 1 <= y <= 4

def neighbors4(p: Coord) -> List[Coord]:
    x, y = p
    cands = [(x+1,y), (x-1,y), (x,y+1), (x,y-1)]
    return [c for c in cands if in_bounds(c)]

@dataclass
class World:
    pits: Set[Coord]
    wumpus: Optional[Coord]   # None falls tot/entfernt
    gold: Coord
    agent_pos: Coord
    agent_dir: str
    arrow_available: bool
    has_gold: bool
    scream_flag: bool  # 1 Zug lang, wenn Wumpus stirbt
    bump_flag: bool    # 1 Zug lang, wenn gegen Wand gelaufen
    alive: bool
    won: bool

    @staticmethod
    def random_world(seed: Optional[int] = None) -> 'World':
        rnd = random.Random(seed)
        cells = [(x, y) for x in range(1, 5) for y in range(1, 5) if not (x == 1 and y == 1)]
        # Platziere 3 Fallgruben
        pits = set(rnd.sample(cells, 3))
        remaining = [c for c in cells if c not in pits]
        # Platziere Wumpus
        wumpus = rnd.choice(remaining)
        remaining = [c for c in remaining if c != wumpus]
        # Platziere Gold
        gold = rnd.choice(remaining)

        return World(
            pits=pits,
            wumpus=wumpus,
            gold=gold,
            agent_pos=(1, 1),
            agent_dir='E',
            arrow_available=True,
            has_gold=False,
            scream_flag=False,
            bump_flag=False,
            alive=True,
            won=False,
        )

    def perceive(self) -> List[Optional[str]]:
        """Gibt [Stench, Breeze, Glitter, Bump, Scream] zurück (Strings oder None)."""
        stench = None
        breeze = None
        glitter = None
        bump = "Bump" if self.bump_flag else None
        scream = "Scream" if self.scream_flag else None

        # Stench (Wumpus lebend): im Feld des Wumpus + Nachbarfelder
        if self.wumpus is not None:
            if self.agent_pos == self.wumpus or self.agent_pos in neighbors4(self.wumpus):
                stench = "Stench"

        # Breeze: neben einer Fallgrube
        for pit in self.pits:
            if self.agent_pos in neighbors4(pit):
                breeze = "Breeze"
                break

        # Glitter: im Feld mit Gold
        if self.agent_pos == self.gold and not self.has_gold:
            glitter = "Glitter"

        # Bump und Scream gelten nur für einen Zug
        # (Rücksetzen macht die Spielschleife nach Ausgabe)
        return [stench, breeze, glitter, bump, scream]

    def step_forward(self) -> None:
        target = forward_from(self.agent_pos, self.agent_dir)
        if in_bounds(target):
            self.agent_pos = target
            self.bump_flag = False
        else:
            # Wand => Bump
            self.bump_flag = True
        # Tod prüfen (Pit oder lebender Wumpus am selben Feld)
        if self.agent_pos in self.pits:
            self.alive = False
        if self.wumpus is not None and self.agent_pos == self.wumpus:
            self.alive = False

    def turn_left(self) -> None:
        self.agent_dir = left_of(self.agent_dir)

    def turn_right(self) -> None:
        self.agent_dir = right_of(self.agent_dir)

    def grab(self) -> None:
        if self.agent_pos == self.gold and not self.has_gold:
            self.has_gold = True

    def shoot(self) -> None:
        if not self.arrow_available:
            return
        self.arrow_available = False
        # Pfeil fliegt geradeaus bis zur Wand
        x, y = self.agent_pos
        dx, dy = 0, 0
        if self.agent_dir == 'N':
            dy = 1
        elif self.agent_dir == 'E':
            dx = 1
        elif self.agent_dir == 'S':
            dy = -1
        elif self.agent_dir == 'W':
            dx = -1

        cx, cy = x, y
        hit = False
        while True:
            cx += dx
            cy += dy
            if not in_bounds((cx, cy)):
                break
            if self.wumpus is not None and (cx, cy) == self.wumpus:
                hit = True
                break
        if hit:
            self.wumpus = None
            self.scream_flag = True

    def climb(self) -> None:
        if self.agent_pos == (1, 1) and self.has_gold:
            self.won = True

    def reveal_map(self) -> str:
        """Nur Debug-Ausgabe (nicht Teil der Wahrnehmung)."""
        def cell_str(x: int, y: int) -> str:
            tags = []
            if (x, y) in self.pits:
                tags.append('P')
            if self.wumpus == (x, y):
                tags.append('W')
            if self.gold == (x, y) and not self.has_gold:
                tags.append('G')
            if (x, y) == self.agent_pos:
                tags.append({'N':'^','E':'>','S':'v','W':'<'}[self.agent_dir])
            return ''.join(tags) if tags else '.'
        rows = []
        for y in range(4, 1-1, -1):
            row = ' '.join(cell_str(x, y) for x in range(1, 5))
            rows.append(row)
        return '\n'.join(rows)

def print_help() -> None:
    print("""
Befehle:
  f  = forward (gehe vorwärts)
  l  = turn_left (90° links drehen)
  r  = turn_right (90° rechts drehen)
  g  = grab (Objekt im Kästchen greifen)
  s  = shoot (Pfeil abschießen; es gibt nur einen)
  c  = climb (Höhle verlassen; nur bei [1,1] — gewinnt nur mit Gold)
  h  = help (dieses Menü)
  d  = debug (Karte anzeigen — Spoiler!)
  q  = quit (Spiel beenden)
Hinweis: Wahrnehmungen kommen als [Stench, Breeze, Glitter, Bump, Scream].
Der Agent kennt seine Koordinaten NICHT.
""")

def main(seed: Optional[int] = None) -> None:
    world = World.random_world(seed)
    print("Willkommen in der Wumpus-Höhle (4×4)!")
    print("Ziel: Hole das Gold und verlasse die Höhle (climb bei [1,1]).")
    print("Start: [1,1], Blickrichtung Osten. Viel Erfolg!")
    print_help()

    # Erste Wahrnehmung (kein Bump/Scream zu Beginn)
    perceptions = world.perceive()
    print("Wahrnehmung:", perceptions)

    while world.alive and not world.won:
        cmd = input("> ").strip().lower()

        if cmd in ("f", "forward"):
            world.step_forward()
        elif cmd in ("l", "left", "turn_left"):
            world.turn_left()
        elif cmd in ("r", "right", "turn_right"):
            world.turn_right()
        elif cmd in ("g", "grab"):
            world.grab()
        elif cmd in ("s", "shoot"):
            prev_wumpus = world.wumpus
            world.shoot()
        elif cmd in ("c", "climb"):
            world.climb()
        elif cmd in ("h", "help"):
            print_help()
        elif cmd in ("d", "debug"):
            print(world.reveal_map())
        elif cmd in ("q", "quit", "exit"):
            print("Spiel beendet.")
            return
        else:
            print("Unbekannter Befehl. 'h' für Hilfe.")
            continue

        # Status nach Aktion
        if not world.alive:
            print("Du bist gestorben! (Fallgrube oder Wumpus)")
            break
        if world.won:
            print("Geschafft! Du hast das Gold sicher nach Hause gebracht. 🏆")
            break

        perceptions = world.perceive()
        print("Wahrnehmung:", perceptions)
        # Flags (Bump/Scream) gelten nur für EINEN Zug -> jetzt zurücksetzen
        world.bump_flag = False
        world.scream_flag = False

        # Kleines Status-Overlay (ohne Position!)
        status = []
        status.append(f"Pfeil: {'ja' if world.arrow_available else 'nein'}")
        status.append(f"Gold im Sack: {'ja' if world.has_gold else 'nein'}")
        print("Status:", " | ".join(status))

if __name__ == "__main__":
    # Optional: seed als Zahl eingeben, sonst zufällig
    try:
        import sys
        seed = int(sys.argv[1]) if len(sys.argv) > 1 else None
    except Exception:
        seed = None
    main(seed)
